<?php

1->调用方式引发的问题
   Eureka: http://serverIp:port/path
   Ribbon: http://serverName/path
   问题: 以上2种模式都需要'手动拼接地址', 那能不能不拼接呀?!


2->Feign
   -) '优点'
       . 简化远程调用
       . 集成了Ribbon
       . 集成了Hystrix

   -) '架构解析'
       我们在学习中经常会碰到这种case，有些开源组件不显山来不露水，乍一看功能很简单，配置起来也不麻烦，让人感觉实现起来也不难。
       实际上我们所看到的只是冰山上的一角，在冰山下面隐藏的巨大基座才是这套技术的全貌。

       就像Feign一样，往往以一个注解开场的项目，背后的故事都不简单。接下来，我们就潜入深海，看看Feign这座冰山的架构全景。

   -) 'A - 构建请求'
                               +---------+   +---------+
                               |  Ribbon |   | Hystrix |
                               +---------Feign---------+
                                           |
                                        动态代理
                                           |
                            +------------------------------+
                            |          Contract            |
                            | MethodHandler  MethodHandler |
                    +-------+------------------------------+--------+
                    |  加密                                    解密   |
                    +-------+                              +--------+
                        |                                       |
                  +----------+                            +----------+
                  |  Request |                            | Response |
                  +----------+                            +----------+

        - 左右护法: 大伙现在看出Feign是个什么腕儿了吗？看那身旁站着Ribbon和Hystrix，左青龙右白虎，给Feign保驾护航。
                  没错，Feign自己兜里就揣着Ribbon和Hystrix两把重武器，引入Feign依赖的同时这两个组件也会被一同引入。
                  Ribbon: 利用负载均衡策略选定目标机器
                  Hystrix: 根据熔断器的开启状态，决定是否发起此次调用

        - 动态代理: Feign是通过一个代理接口进行远程调用，这一步就是为了构造接口的动态代理对象，用来代理远程服务的真实调用，
                   这样你就可以像调用本地方法一样发起HTTP请求，不需要像Ribbon或者Eureka那样在方法调用的地方提供服务名。在Feign中动态代理是通过
                   'Feign.build'返回的构造器来装配相关参数，然后调用ReflectFeign的newInstance方法创建的。这里就应用到了Builder设计模式，
                   稍后番外篇会给大家说一个实现Builder模式的简单方法。

        - contract: 协议，顾名思义，就像HTTP协议，RPC协议一样，Feign也有自己的一套协议的规范，只不过他解析的不是HTTP请求，而是上一步提到的动态代理类。
                    通过解析动态代理接口+Builder模式，Contract协议会构造复杂的元数据对象MethodMetadata，这里面包含了动态代理接口定义的所有特征。
                    接下来，根据这些元数据生成一系列MethodHandler对象用来处理Request和Response请求。
                    . Contract具有高度可扩展性，可以经由对Contract的扩展，将Feign集成到其他开源组件之中。

   -) 'B - 发起调用'
                                     +-----------+
                                     |  Request  |
                                     +-----------+
                                           |
                                      +---------+
                                      |  拦截器  |
                                      +---------+
                                           |
                            +-----------------------------+
                            |           发送请求           |
                 Ribbon -----------> 重试策略  降级策略 <--------- Hystrix
                            |           日志组件           |
                            +-----------------------------+

        - 拦截器:
          拦截器是Spring处理网络请求的经典方案，Feign这里也沿用了这个做法，通过一系列的拦截器对Request和Response对象进行装饰，
          比如通过RequestInterceptor给Request对象构造请求头。整装待发之后，就是正式发起调用的时候了。

        - 发起请求:
          又到了左右护法的出场镜头了。这哼哈二将绝不放过开头和结尾两处重要镜头，正所谓'从头到尾'都参与了进来。
          . Feign这里借助Ribbon的配置重试器实现了重试操作，可以指定对当前服务节点发起重试，也可以让Feign换一个服务节点重试。
          . Feign接口在声明时可以指定Hystrix的降级策略实现类，如果达到了Hystrix的超时判定，或得到了异常结果，将执行指定的降级逻辑。
            Hystrix降级熔断的内容，将在下一个大章节和大家见面。


3->使用
   -) 'pom'
       <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
       </dependency>

   -) '全局注解'
       @EnableFeignClients

   -) '服务接口'
       @FeignClient(name = "eureka-client")
       public interface IService {
          @GetMapping("/sayHello")
          String sayHello();
       }


4->代理
   所谓动态代理是相对于静态编译来说
   java中，如果我们编译期无法确定对象类型，只能等待程序执行时，也就是运行期间才能知道，那么称之为动态获取对象。
   feign的所有代理实例通过ReflectiveFeign.newInstance创建，底层采用builer模式，将@FeignClient接口的特征、方法名、参数等信息提取，
   拼装成java反射机制中通用的Method类
   在newInstance过程中，Feign通过JDK的InvocationHandler接口（所有动态代理方案几乎都和它有关联），将自己的Handler和上一步组装的Method进行关联，实现代理。
   spring的AOP有两种动态代理方式
   . 'JDK动态代理' (通过'JdkDynamicAopProxy'实现)
   特点:
   a. 实现InvocationHandler接口，接管invoke方法现实先自己的业务逻辑，所有调用都会被传递到InvocationHandler的invoke方法，
      通过Proxy.newProxyInstance获取动态代理对象
   b. 被代理的对象必须实现某个接口，不能代理无接口的类

   'CGLIB实现动态代理'
   a. 代理对象性能比JDKDynamic快，但创建代理对象时间长
   b. 如果类没有实现接口或者是单例模式的类不需要重复创建可采用此方法。


5->EnableFeignClients注解
   - 打在启动类上, 可以指定'扫描的包 | 类', 这样在引入'别的项目'的话, 就可以配置这2个属性进行扫描.
     basePackages = {}, basePackageClasses = {}
   - 如果只是空注解,没有指定任何值, 那么项目启动就会'扫描项目启动类.所有包下所有类 - @FeignClient'是否存在此注解!
     并且该类'必须是接口!'


6->使用
   说明: 当想要将某个服务提供给其余服务调用的时候, 就创建接口, 并打上注解!!!
   -) '接口层'
       通常由服务提供方的团队来定义。我们在定义接口方法的时候，就通过@RequestMapping注解，将访问路径的URL(比如“/getUser”)，
       连同HTTP Method（GET/POST/PUT/DELETE）配置到接口方法上，同时加上@RequestParam指定传入参数的name。
       当然了，你也可以将@FeignClient注解直接配置在接口上，这样的话调用方甚至不需要声明本地接口，而只要配置一个package scan的路径，
       将你的'接口扫描进来'就可以加载了，不过当你决定包含@FeignClient的时候要注意两件事
       . @FeignClient将引入openfeign的pom依赖，可能与调用方使用的版本不一致，如果调用方引入接口层后发生版本冲突导致项目启动失败的话，
         需要调用方排除导致冲突的依赖包（在pom中使用exclude排除冲突项）

       . 尽量不要在共享接口层指定fallback实现类，保持接口层的单一职责，因为降级后的业务逻辑通常应该交给服务调用方来决定，
         让调用方在自己声明的接口上添加@FeignClient注解并指定降级处理类。

   -) 'Controller'
       作为服务提供方，可以将Controller指向接口，这样也就不用在Controller的方法上加一堆配置参数了。当然，如果需要加上额外的配置项，
       你也可以在Controller里覆盖接口注释，但是一定要确保和接口层的主要配置（路径，HTTP Method）是相同的，假如两处注解的path设置不一样，
       那么业务调用方可就要404了。

   -) '调用方'
       根据外部接口的配置，有两个方法可以进行对接
       . 假如外部接口没有声明@FeignClient，那我们只要声明一个本地接口并加上@FeignClient注解，然后继承外部接口，不用声明任何具体方法，
         通过@Autowired注入后就可以进行调用了。
         (注意: 这里外部服务要注册到'eureka',我们的调用才可以找到调用路径.)

       . 假如接口层已经添加了@FeignClient注解，这里你只需要确保在加载项目时把包路径扫到就可以了，如果接口层和调用方都使用了相同的包路径，
         那恭喜你，什么都不要做，直接在业务代码中使用@Autowired加载接口就可以。不过就像前面提到的，业务调用方往往需要实现特殊的降级逻辑，
         这就要在@FeignClient接口上指定'自定义的fallback实现类'，那么就只好再声明一个本地接口了。

   -) '小坑'
       这里有一个小坑要留意下，如果调用方声明的本地接口继承了外部接口，并且两个地方同时声明了@FeignClient的话，在使用@Autowired加载外部注解类时项目会启动失败。
       因为两处@FeignClient接口都默认primary=true，Spring不知道到底该加载哪个好。


7->Feign引用的问题
   springcloud版本: G
   问题描述: 父接口打上'@FeignClient注解', 然后'子类继承后'也打上了'@FeignClient注解', 那么启动就会报错.(因为spring发现了2个,不知道该用谁了!)
   解决: 配置文件(application.yml)
   spring.main.allow-bean-definition-overriding: true


8->服务调用的超时重试
   -) '重试机制的配置搭档-Ribbon'
       # 这个参数指定了什么HTTP Method可以进行Retry，这里为了演示方便才设置为true，表示不管GET还是POST什么都能重试。(注意: 要小心配置! 需要保证接口[幂等性])
       # 真实的生产环境往往只是GET请求可以重试，或者'实现了幂等性'的其他类型请求.
       feign-service-provider.ribbon.OkToRetryOnAllOperations=true
       # 超时判定的第一个参数（单位ms），创建会话的连接时间。注意，这个不是服务的响应时间，而是本机和服务建立一个Connection所花费的时间，
       # 如果连接超时则直接进行重试.
       feign-service-provider.ribbon.ConnectTimeout=1000
       # 超时判定的第二个参数，服务响应时间。当连接建立好之后，如果对方服务没有在规定时间内返回，则直接进行重试.
       feign-service-provider.ribbon.ReadTimeout=2000
       # 求极限关键参数之一，当前节点重试次数。这里重试次数为2，那么在首次调用超时以后，
       # 会再次向同一个服务节点发起最多2次重试（总共向当前节点1+2=3次请求）
       feign-service-provider.ribbon.MaxAutoRetries=2
       # 求极限关键参数之二，换N个节点重试。这里N=2，就是说在当前机器调用超时后，Feign将最多换N台机器发起调用
       #（注意，这里将和第一个参数共同作用，也就是说，在新机器上超时后，会继续重试MaxAutoRetries+1次）
       feign-service-provider.ribbon.MaxAutoRetriesNextServer=2
       注意: 如果该服务开启了以上的配置的话, 那么该服务通过[Feign]调用[其余微服务]接口的一旦发生超时的话, 就会
            按照自定义[超时配置]进行重试机制!!!

    -) '极值函数'
        MAX(Response Time) = (ConnectTimeout + ReadTimeout) * (MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)



9->超时重试配置
   -) 'Ribbon'
      '全局配置(推荐)'
       ribbon:
         ConnectTimeout: 1000
         ReadTimeout: 5000

      '单服务配置(服务名) . (优先级 > 全局)'
       eureka-client:
         ribbon:
           ConnectTimeout: 1000
           ReadTimeout: 5000
           MaxAutoRetries: 1
           MaxAutoRetriesNextServer: 2
           OkToRetryOnAllOperations: false

   -) 'Feign'
       # 此配置优先级 > Ribbon 的优先级
       feign:
        client:
          config:
           '全局判定'
           default:
             connectTimeout: 1000
             readTimeout: 5000
           '单服务(服务名)'
           eureka-client:
             connectTimeout: 1000
             readTimeout: 5000

   说明: Feign老大哥的配置是'一级优先', 如果Feign和Ribbon针对同一属性都配置值的话, 那么最终以'Feign为准'
   使用: 一般我们项目中用'Ribbon'进行'全局重试配置'!
